/*
 * Created on Mar 6, 2010
 */
package com.mgmaps.app;

import java.io.IOException;
import java.util.TimerTask;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.*;
import com.mgmaps.gps.GPS;
import com.mgmaps.gps.GPSReceiver;
import com.mgmaps.tools.GeoTools;
import com.mgmaps.utils.*;
import com.nutiteq.BasicMapComponent;
import com.nutiteq.MapComponent;
import com.nutiteq.components.MapPos;
import com.nutiteq.components.Place;
import com.nutiteq.components.WgsPoint;
import com.nutiteq.controls.ControlKeys;
import com.nutiteq.listeners.MapListener;
import com.nutiteq.maps.GeoMap;
import com.nutiteq.net.DefaultDownloadStreamOpener;

public abstract class APPCanvas extends Canvas implements MapListener, CommandListener {
  
  public static APPCanvas instance;
  
  protected final boolean pointerEvents;
  protected int mapWidth;
  protected int mapHeight;
  protected MapComponent map;
  protected int mapType;

  protected volatile boolean initialized;
  protected volatile boolean inWelcome;
  protected volatile boolean mapInitialized;
  
  // resize
  protected boolean sizeWasChanged;
  protected int oldw;
  protected int oldh;
  
  // key hints
  protected int[][] KH_INT = { { 10, 11 } };
  protected String[][] KH_STR = { { LS.get("app.msg.zoomout"), LS.get("app.msg.zoomin") } };
  protected int currentKHNumber;
  protected Image keyImage;
  protected int keyIconW;
  protected int keyIconH;
  protected static final int NUM_KEYS = 12;

  // font
  protected final Font font;
  protected final int fontHeight;
  
  // invalidate constants
  public static final int INV_SHOW = 0x01;    // entire canvas is shown/hidden
  public static final int INV_PLACE = 0x02;   // a place was changed
  public static final int INV_MAP = 0x04;     // the map was changed (new tiles loaded, zoomed, etc.)
  public static final int INV_WELCOME = 0x08; // changes in the welcome screen
  public static final int INV_OVERLAY = 0x10; // an overlay was changed (eg. GPSPlace) 
  public static final int INV_MESSAGE = 0x20; // the message area was changed
  public static final int INV_GPS = 0x40;     // GPS position changed (could be replaced by INV_ANIM)
  public static final int INV_ANIM = 0x80;    // animation (ie. quick update)
  public static final int INV_ANY = 0xFFFF;   // anything invalid
  protected int invalid;
  
  protected static final int CLICK_NONE   = 0;
  protected static final int CLICK_MAP    = -1;
  protected static final int CLICK_PLACE  = -2;
  protected static final int CLICK_BUTTON = -3;

  // touch/drag fix
  protected int clickSource;
  protected boolean dragged;
  protected int dragStartX;
  protected int dragStartY;
  
  // double buffer
  protected Image dblbuf;
  protected Graphics dblbufg;

  // images
  protected int titleHeight;
  protected int titleWidth;
  protected int titlePadWidth;
  protected Image splash;
  protected Image title;
  protected Image titlePad;
  protected int splashTimerInterval;
  protected int welcomeNumOptions;
  protected Image[] welcomeIcons;
  
  // touch zoom
  protected int touchZoomWidth;
  protected int touchZoomHeight;
  protected int touchButtonWidth;
  protected int touchBGWidth;
  protected int touchBGHeight;
  protected int touchMul;
  
  // common colors
  protected int colorWhite = 0xFFFFFF;
  protected int colorBlack = 0x000000;
  protected int colorYellow = 0xFFFECC;
  protected int colorBlue = 0x6E98E5;
  protected int colorRed = 0xFF4040;
  protected int colorTrans = 0xFFFFFFFF;
  
  // objects
  protected ExtKeyHandler kh;
  protected DelayedMapListener ml;
  protected WelcomeScreen welcomeScreen;
  protected GPS gps;
  protected ButtonOverlay btnOverlay;
  
  // places
  protected static final APPPlace[] emptyPlaces = {};
  protected volatile APPPlace[] places = emptyPlaces;
  protected APPPlace selectedPlace;

  protected abstract void initializeStart();
  protected abstract void initializeDownloader();
  protected abstract void initializeCommands();
  protected abstract void initializeOther1();
  protected abstract void initializeOther2();
  protected abstract WelcomePaintArgs createWelcomePaintArgs();
  protected abstract boolean firstTimeInitialization();
  protected abstract void initializeMapStart();
  protected abstract void initializeMapEnd();
  
  protected abstract String getMapLicenseKey();
  protected abstract String getMapLicenseVendor();
  protected abstract String getMapLicenseAppName();
  
  protected abstract void loadImages() throws IOException;
  protected abstract void setUserAgent(DefaultDownloadStreamOpener dso);
  protected abstract void setMapSize(int w, int h);
  protected abstract void setMapPosition();
  protected abstract GeoMap getGeoMap(int mt);
  protected abstract void setMapAttributes();
  protected abstract void loadMapCache();
  protected abstract void loadOther();
  
  protected abstract void addCommands();
  protected abstract void setCanvas();
  protected abstract String[] welcomeScreenOptions();

  protected abstract void buttonOverlayAction(int type, int selected);
  protected abstract void welcomeScreenAction(int selected);
  protected abstract void placeClickedAction();

  protected abstract boolean keyPressedCustom(int keyCode, int action);
  protected abstract boolean keyReleasedCustom(int keyCode, int action);
  protected abstract boolean keyRepeatedCustom(int keyCode, int action);
  protected abstract boolean getClickSourcePlaceClicked();
  protected abstract void pointerPressedCustom(int x, int y);
  protected abstract void pointerReleasedCustom(int x, int y);
  protected abstract void pointerReleasedCustomMap(int x, int y);
  protected abstract boolean pointerReleasedKeyRelease();
  protected abstract void pointerDraggedCustom(int x, int y);
  protected abstract boolean pointerDraggedDisableGPSSync();

  public abstract void notifyGPSUpdate(boolean force);
  
  protected abstract void paintMap(Graphics g);
  
  protected abstract void downloadPlaces(boolean force);
  
  protected class DummyStarter implements Runnable {
    public void run() {
      if (APPSettings.isBlackberry && APPSettings.featureOnline)
        try {
          final HttpConnection c = (HttpConnection) Connector.open(APPSettings.appURL + APPSettings.httpExtension);
          c.openInputStream();
          c.getLength();
        } catch (IOException ignore) {
        }
        
      initialize();
    }
  }
  
  protected class SplashTimerTask extends TimerTask {
    public void run() {
      new Thread(new DummyStarter()).start();
    }
  }
  
  /**
   * Called when the screen size changes.
   * @param w new width
   * @param h new height
   * @see javax.microedition.lcdui.Displayable#sizeChanged(int, int)
   */
  protected void sizeChanged(int w, int h) {
    super.sizeChanged(w, h);

    sizeWasChanged = true;
    invalidate(INV_SHOW);
  }
  
  protected void sizeChangedAction(int w, int h) {
    ExtLogger.debug("Size changed to " + w + "x" + h);
    
    if (dblbuf != null) {
      dblbufg = null;
      dblbuf = Image.createImage(w, h);
      dblbufg = dblbuf.getGraphics();
    }
    
    oldw = w;
    oldh = h;
    
    setMapSize(w, h);

    if (btnOverlay != null)
      btnOverlay.sizeChanged(mapWidth, mapHeight);

    if (APPSettings.featureMap && map != null && map.getMiddlePoint() != null)
      map.resize(mapWidth, mapHeight);
    
    // set welcome screen size and icons
    if (welcomeScreen != null)
      welcomeScreen.setSize(w, h-titleHeight);
  }
  
  /**
   * Getter for map.
   * 
   * @return map
   */
  public MapComponent getMap() {
    return map;
  }
  
  /**
   * Getter for mapType.
   * 
   * @return mapType
   */
  public int getMapType() {
    return mapType;
  }
  
  /**
   * Constructor for APPCanvas.
   */
  public APPCanvas() {
    super();
    
    currentKHNumber = 0;
    invalid = 0;
    
    instance = this;
    inWelcome = true;
    mapInitialized = false;
    
    // load commands
    initializeCommands();

    oldw = getWidth();
    oldh = getHeight();
    pointerEvents = hasPointerEvents();

    // load images
    try {
      loadImages();
    }
    catch (IOException ie) {
      ExtLogger.handle(ie, true, "Error loading images");
    }
    
    font = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_PLAIN, Font.SIZE_SMALL);
    fontHeight = font.getHeight();

    // full screen
    if (!APPSettings.isBlackberry /* && !APPSettings.isNokiaTouch */)
      setFullScreenMode(true);
    
    // map initialization
    if (APPSettings.featureMap) {
      // double buffer
      dblbuf = Image.createImage(oldw, oldh);
      dblbufg = dblbuf.getGraphics();
      
      // set map width and height
      setMapSize(oldw, oldh);

      // map
      try {
        map = new MapComponent(getMapLicenseKey(), getMapLicenseVendor(), getMapLicenseAppName(),
            mapWidth, mapHeight,
            new WgsPoint(APPSettings.savePointX, APPSettings.savePointY), APPSettings.savePointZ);
      }
      catch (Exception ex) {
        ExtLogger.handle(ex, true, "Error initializing map");
        return;
      }
      
      // download stream opener
      if (APPSettings.featureOnline) {
        final DefaultDownloadStreamOpener dso = new DefaultDownloadStreamOpener(APPSettings.httpExtension);
        setUserAgent(dso);
        map.setDownloadStreamOpener(dso);
      }
      
      // map type
      setMapType();
      
      // screen position
      
      // position
      setMapPosition();
      
      // map cache
      loadMapCache();
      
      // other map settings
      setMapAttributes();
    }

    if (APPSettings.featureMap)
      addCommands();
    setCommandListener(this);
      
    // load other stuff
    loadOther();
      
    // splash screen hide timer
    APPSettings.timer.schedule(new SplashTimerTask(), splashTimerInterval);
  }

  /**
   * Initialize (after the splash screen).
   */
  protected void initialize() {
    try {
      if (APPSettings.firstTime)
        if (!firstTimeInitialization())
          return;

      // initialize downloader, enqueue whatever is needed
      initializeDownloader();
      
      // other initialization
      initializeOther1();
      
      // welcome screen
      createWelcomeScreen();
      inWelcome = true;
      
      // initialize gps
      if (APPSettings.featureGPS)
        gps = new GPS(); // don't move map on first fix
      
      // other initialization
      initializeOther2();

      initialized = true;
      splash = null;
      invalidate(INV_SHOW);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }
  
  protected void createWelcomeScreen() {
    final int w = oldw;
    final int h = oldh-titleHeight;
    WelcomePaintArgs pa = createWelcomePaintArgs();
    welcomeScreen = new WelcomeScreen(welcomeScreenOptions(), welcomeIcons, pa, w, h);
  }
  
  /**
   * Initialize the map the first time it is used.
   */
  protected void initializeMap() {
    if (!APPSettings.featureMap)
      return;
    
    if (!mapInitialized) {
      initializeMapStart();

      // start mapping
      map.startMapping();

      // old full screen call
      map.resize(mapWidth, mapHeight);

      // update map listener
      ml.update(true);
      
      initializeMapEnd();
    
      mapInitialized = true;
    }
    
    addCommands();
    
    setCanvas();
    invalidate(INV_SHOW);
  }

  /**
   * If the library says we need a repaint, invalidate with INV_MAP.
   * 
   * @param mapIsComplete whether the map is complete or is still downloading
   * @see com.nutiteq.listeners.MapListener#needRepaint(boolean)
   */
  public void needRepaint(boolean mapIsComplete) {
    invalidate(INV_MAP);
  }
  
  /**
   * Invalidate the screen.
   * @param what what became invalid
   */
  public synchronized void invalidate(int what) {
    invalid |= what;

    // are we displayed?
    if (Display.getDisplay(APPMidlet.instance).getCurrent() != this)
      return;
    
    // not initialized and trying to invalidate INV_ANIM? return to avoid quick flicker/update
    if (!initialized && (what & INV_ANIM) != 0)
      return;
    
    // full screen update if place changed
    // if ((what & INV_PLACE) != 0)
    //   map.fullScreenUpdate();
    
    repaint();
  }
  
  /**
   * Is anything invalid on the map?
   * 
   * @param what what to check
   * @return true if "what" is invalid
   */
  protected synchronized boolean isInvalid(int what) {
    return (invalid & what) != 0;
  }

  /**
   * Set invalid to 0, synchronized.
   */
  protected synchronized void clearInvalid() {
    invalid = 0;
  }

  /**
   * Notify that the app is exiting.
   */
  public void notifyExit() {
    if (gps != null)
      gps.notifyExit();
    if (ml != null)
      ml.setQuitting();
  }
  
  /**
   * Paint the key hints.
   */
  protected void paintKeyHints(Graphics g) {
    int kn = -1;
    // search for a valid key being displayed
    while(true) {
      kn = currentKHNumber % KH_STR.length;
      if (KH_STR[kn].length == 0)
        currentKHNumber++;
      else
        break;
    }
    // spacing = 2 between icon & string, 10 between strings
    int sw = (12+keyIconW) * KH_INT[kn].length - 10;
    for (int i=0; i<KH_INT[kn].length; i++)
      sw += font.stringWidth(KH_STR[kn][i]);
    int sh = Math.max(keyIconH, fontHeight);
    
    int startX = ((mapWidth-sw)>>1)-2;
    int currentX = startX+2;
    // int yy = titleHeight + 10 + fontHeight;
    int yy = titleHeight + 5;
    drawWindow(g, startX, yy-1, sw+4, sh+4);
    for (int i=0; i<KH_INT[kn].length; i++) {
      g.setClip(currentX, yy+1, keyIconW, keyIconH);
      g.drawImage(keyImage, currentX-KH_INT[kn][i]*keyIconW, yy+1, Graphics.TOP|Graphics.LEFT);
      g.setClip(startX+1, yy, sw+2, sh+2);
      currentX += keyIconW+2;
      g.drawString(KH_STR[kn][i], currentX, yy+1, Graphics.TOP | Graphics.LEFT);
      currentX += font.stringWidth(KH_STR[kn][i]) + 10;
    }
  }
  
  protected void showMessage(final Graphics g, final String msg) {
    final int w = font.stringWidth(msg);
    final int h = fontHeight;
    final int x = (mapWidth-w)/2;
    final int y = titleHeight + 10 + Math.max(keyIconH, fontHeight);
    g.setColor(colorYellow);
    g.fillRect(x, y, w, h);
    g.setColor(colorBlack);
    g.drawString(msg, x, y, Graphics.TOP | Graphics.LEFT);
  }
  
  /**
   * Draw a window.
   * @param g graphics object
   * @param x left coordinate
   * @param y top coordinate
   * @param w width
   * @param h height
   */
  protected void drawWindow(Graphics g, int x, int y, int w, int h) {
    // background
    g.setColor(colorYellow);
    g.fillRect(x, y, w, h);
    // border
    g.setColor(colorBlue);
    g.drawRect(x, y, w, h);
    // set clip
    g.setClip(x+1, y+1, w-2, h-2);
    g.setColor(colorBlack);
  }
  
  protected void paint(final Graphics g) {
    try {
      
      // size changed check
      int w = getWidth();
      int h = getHeight();
      
      if (sizeWasChanged) {
        sizeWasChanged = false;
        sizeChangedAction(w, h);
      }
      else if (w != oldw || h != oldh) {
        sizeChangedAction(w, h);
      }
      
      // splash screen
      if (!initialized) {
        if (splash != null) {
          // paint splash
          g.setColor(0xFFFFFFFF);
          g.setClip(0, 0, getWidth(), getHeight());
          g.fillRect(0, 0, getWidth(), getHeight());
          g.drawImage(splash, (getWidth() - splash.getWidth()) / 2,
            (getHeight() - splash.getHeight()) / 2, Graphics.TOP | Graphics.LEFT);
        }
        return;
      }
      
      if (inWelcome) {
        // HACK set welcome screen size and icons
        if (welcomeScreen != null)
          welcomeScreen.setSize(w, h-titleHeight);

        paintWelcome(g);
        return;
      }
      
      if (APPSettings.featureMap)
        paintMap(g);
      
    }
    catch (Exception ex) {
      ExtLogger.handle(ex, false, "Exception in paint");
    }
  }
  
  protected void paintWelcome(final Graphics g) {
    g.setClip(0, 0, mapWidth, titleHeight);
    paintTitleBar(g);
      
    // welcomeScreen.setSize(getWidth(), getHeight()-titleHeight);
    welcomeScreen.paint(g, 0, titleHeight);
  }
  
  protected void paintTitleBar(final Graphics g) {
    // top
    for (int i=0; i<mapWidth; i+=titlePadWidth)
      g.drawImage(titlePad, i, 0, Graphics.TOP|Graphics.LEFT);
    g.drawImage(title, (mapWidth-titleWidth)/2, 0, Graphics.TOP | Graphics.LEFT);
  }
  
  /**
   * Called when a button is selected in button overlays.
   * @param selected selected button index
   */
  protected void buttonOverlayAction(int selected) {
    buttonOverlayAction(btnOverlay.getType(), selected);    
  }

  protected void keyPressed(int keyCode) {
    if (!initialized)
      return;
    
    final int action = kh.getKeyActionCode(keyCode);
    
    if (inWelcome) {
      if (action == ControlKeys.MOVE_LEFT_KEY) {
        welcomeScreen.keyLeft();
        invalidate(INV_WELCOME);
      }
      else if (action == ControlKeys.MOVE_RIGHT_KEY) {
        welcomeScreen.keyRight();
        invalidate(INV_WELCOME);
      }
      else if (action == ControlKeys.MOVE_DOWN_KEY) {
        welcomeScreen.keyDown();
        invalidate(INV_WELCOME);
      }
      else if (action == ControlKeys.MOVE_UP_KEY) {
        welcomeScreen.keyUp();
        invalidate(INV_WELCOME);
      }
      else if (action == ControlKeys.SELECT_KEY) {
        invalidate(INV_WELCOME);
      }
      // else
      //   commandAction(LogForm.SHOW_LOG, this);
      return;
    }
    
    else if (btnOverlay != null) {
      if (btnOverlay.isPressed())
        return;
      
      if (action == ControlKeys.MOVE_LEFT_KEY && btnOverlay.isHorizontal() ||
          action == ControlKeys.MOVE_UP_KEY && btnOverlay.isVertical()) {
        btnOverlay.prev();
        invalidate(INV_OVERLAY);
      }
      else if (action == ControlKeys.MOVE_RIGHT_KEY && btnOverlay.isHorizontal() ||
          action == ControlKeys.MOVE_DOWN_KEY && btnOverlay.isVertical()) {
        btnOverlay.next();
        invalidate(INV_OVERLAY);
      }
      else if (action == ControlKeys.SELECT_KEY) {
        btnOverlay.setPressed(true);
        invalidate(INV_OVERLAY);
      }
      return;
    }
    
    else if (keyPressedCustom(keyCode, action))
      return;
    
    if (APPSettings.featureMap) {
      if (APPSettings.featureGPS && kh.isArrowKeyAction(action))
        APPSettings.gpsSyncEnabled = false;
    
      if (map != null) {
        if (action != ControlKeys.SELECT_KEY)
          map.keyPressed(keyCode);
      }
      
      if (APPSettings.featureGPS && kh.isZoomKeyAction(action)) {
        final GPSPlace gpsPlace = gps.getPlace();
        if (APPSettings.gpsEnabled && gpsPlace != null)
          gpsPlace.updateMapPos();
      }
    }
  }

  protected void keyReleased(int keyCode) {
    if (!initialized)
      return;

    final int action = kh.getKeyActionCode(keyCode);
    if (inWelcome) {
      if (action == ControlKeys.SELECT_KEY) {
        welcomeScreenAction(welcomeScreen.getSelectedIndex());
        return;
      }
    }
    else if (btnOverlay != null) {
      if (action == ControlKeys.SELECT_KEY) {
        btnOverlay.setPressed(false);
        buttonOverlayAction(btnOverlay.selected);
      }
      return;
    }
    else if (keyReleasedCustom(keyCode, action))
      return;
    
    if (map != null) {
      if (action == ControlKeys.SELECT_KEY)
        mapClicked(null);
      else
        map.keyReleased(keyCode);
    }
  }

  protected void keyRepeated(int keyCode) {
    if (!initialized)
      return;
    
    final int action = kh.getKeyActionCode(keyCode);
    if (inWelcome) {
      if (action != ControlKeys.SELECT_KEY)
        keyPressed(keyCode);
      return;
    }
    else if (btnOverlay != null) {
      if (action != ControlKeys.SELECT_KEY)
        keyPressed(keyCode);
      return;
    }
    else if (keyRepeatedCustom(keyCode, action))
      return;

    if (map != null && action != ControlKeys.SELECT_KEY)
      map.keyRepeated(keyCode);
  }

  protected int getClickSource(final int x, final int y) {
    if (btnOverlay != null)
      return CLICK_BUTTON;
    /*
    if (x >= (mapWidth/2-touchArrow/2) && x < (mapWidth/2-touchArrow/2+touchArrow) && y >= PADDING && y < PADDING+touchArrow)
      return UP;
    if (x >= PADDING && x < PADDING+touchArrow && y >= (mapHeight/2-touchArrow/2) && y < (mapHeight/2-touchArrow/2+touchArrow))
      return LEFT;
    if (x >= mapWidth-touchArrow-PADDING && x < mapWidth-PADDING && y >= (mapHeight/2-touchArrow/2) && y < (mapHeight/2-touchArrow/2+touchArrow))
      return RIGHT;
    if (x >= (mapWidth/2-touchArrow/2) && x < (mapWidth/2-touchArrow/2+touchArrow) && y >= mapHeight-touchArrow-PADDING && y < mapHeight-PADDING)
      return DOWN;
    */
    if (y < titleHeight)
      return CLICK_NONE;
    if (y >= mapHeight+titleHeight) {
      if (x >= (mapWidth/2-touchZoomWidth*touchMul) && x < mapWidth/2)
        return KEY_POUND;
      if (x >= mapWidth/2 && x < (mapWidth/2+touchZoomWidth*touchMul))
        return KEY_STAR;
      if (x >= 9 && x < 9+touchButtonWidth*touchMul)
        return KEY_NUM9; // left
      if (x >= mapWidth-9-touchButtonWidth*touchMul && x < mapWidth-9)
        return KEY_NUM6; // right
      return CLICK_NONE;
    }

    final APPPlace place = findPlaceAt(x, y);
    if (place != null && getClickSourcePlaceClicked()) {
      APPSettings.gpsSyncEnabled = false;
      selectedPlace = place;
      return CLICK_PLACE;
    }
    
    // if (checkPlaceClicked())
    //   return CLICK_PLACE;

    return CLICK_MAP;
  }

  protected void pointerPressed(int x, int y) {
    if (!initialized)
      return;
    
    if (inWelcome) {
      // this selects the option
      welcomeScreen.getClickedOption(x, y-titleHeight);
      invalidate(INV_WELCOME);
      return;
    }
    
    // remember drag start
    dragStartX = x;
    dragStartY = y;
    dragged = false;
    
    // check click source -- avoid multiple pointer pressed before pointer released
    if (clickSource != CLICK_NONE)
      return;
    clickSource = getClickSource(x, y);
    
    if (clickSource > 0)
      keyPressed(clickSource);
    
    else if (btnOverlay != null && clickSource == CLICK_BUTTON) {
      final int clickedButton = btnOverlay.getClickedButtonIndex(x, y);
      btnOverlay.setPressed(clickedButton >= 0);
      invalidate(INV_OVERLAY);
    }
    
    /*
    if (clickSource == Canvas.UP || clickSource == Canvas.LEFT || clickSource == Canvas.RIGHT || clickSource == Canvas.DOWN)
      Settings.gpsSyncEnabled = false;
    */
    
    pointerPressedCustom(x, y);
  }

  protected void pointerReleased(int x, int y) {
    if (!initialized)
      return;
    
    if (inWelcome) {
      final int option = welcomeScreen.getClickedOption(x, y-titleHeight);
      invalidate(INV_WELCOME);
      if (option >= 0)
        welcomeScreenAction(option);
      return;
    }
    
    if (clickSource > 0 && pointerReleasedKeyRelease())
      keyReleased(clickSource);
    else if (btnOverlay != null) {
      btnOverlay.setPressed(false);
      if (clickSource == CLICK_BUTTON && !dragged) {
        // get clicked button
        final int index = btnOverlay.getClickedButtonIndex(x, y);
        if (index >= 0)
          buttonOverlayAction(index);
      }
    }
    else if (clickSource == CLICK_PLACE)
      placeClickedAction();
    else {
      // send pointer released event only if dragged
      if (map != null && clickSource == CLICK_MAP) {
        if (dragged)
          map.pointerReleased(x, y);
        pointerReleasedCustomMap(x, y);
      }
    }
    
    pointerReleasedCustom(x, y);
    
    clickSource = CLICK_NONE;
  }

  protected void pointerDragged(int x, int y) {
    if (!initialized)
      return;
    
    if (inWelcome) {
      // this selects the option
      welcomeScreen.getClickedOption(x, y-titleHeight);
      invalidate(INV_WELCOME);
      return;
    }
    
    // real drag?
    if (Math.abs(x-dragStartX) < BasicMapComponent.FINGER_CLICK_TOLERANCE &&
        Math.abs(y-dragStartY) < BasicMapComponent.FINGER_CLICK_TOLERANCE)
      // no, return and ignore the dragged event
      return;
    
    switch (clickSource) {
      case CLICK_MAP:
        // send pressed event if dragged was not set yet
        if (!dragged) {
          if (map != null)
            map.pointerPressed(dragStartX, dragStartY);
          dragged = true;
        }

        // dragged and not in report trap/button overlay, disable gps sync
        if (btnOverlay == null && pointerDraggedDisableGPSSync())
          APPSettings.gpsSyncEnabled = false;
        
        // send dragged event
        if (map != null)
          map.pointerDragged(x, y);
        
        break;
        
      case CLICK_BUTTON:
        dragged = true;
        btnOverlay.doScroll(dragStartY-y);
        dragStartY = y;
        final int newClickedButton = btnOverlay.getClickedButtonIndex(x, y);
        btnOverlay.setPressed(newClickedButton >= 0);
        invalidate(INV_OVERLAY);
        break;
    }
    
    pointerDraggedCustom(x, y);
  }
  
  protected APPPlace findCenteredPlace() {
    return findPlaceAt(mapWidth/2, mapHeight/2+titleHeight);
  }
  
  /**
   * Find the place at (x,y). Ignore GPS.
   * @param x x position (screen coords)
   * @param y y position (screen coords)
   * @return
   */
  protected APPPlace findPlaceAt(final int x, final int y) {
    final APPPlace[] tmpPlaces = places;
    
    // find center mp
    final int zoom = map.getZoom();
    final WgsPoint p = map.getCenterPoint();
    final MapPos mp = map.getMap().wgsToMapPos(GeoTools.toPoint(p), zoom);
    
    // update to mp for point
    final int mpx = mp.getX()+x-(mapWidth/2);
    final int mpy = mp.getY()+y-(mapHeight/2)-titleHeight;
    
    // find place
    APPPlace found = null;
    int foundDist = 0;
    for (int i=0; i<tmpPlaces.length; i++) {
      if (tmpPlaces[i] == null)
        continue;
      Place pl = tmpPlaces[i].getPlace();
      if (pl == null)
        continue;
      
      final WgsPoint p2 = pl.getWgs();
      final MapPos mp2 = map.getMap().wgsToMapPos(GeoTools.toPoint(p2), zoom);
      final int iw = tmpPlaces[i].getIconWidth();
      final int ih = tmpPlaces[i].getIconHeight();
      final int distX = Math.abs(mp2.getX()-mpx-(tmpPlaces[i].getAnchorX()-(iw>>1)-(iw&1)));
      final int distY = Math.abs(mp2.getY()-mpy-(tmpPlaces[i].getAnchorY()-(ih>>1)-(ih&1)));
      // found
      if (distX <= iw/2 && distY <= ih/2 && (found == null || distX*distX+distY*distY < foundDist)) {
        found = tmpPlaces[i];
        foundDist = distX*distX+distY*distY;
      }
    }
    
    return found;
  }

  public void mapClicked(WgsPoint p) {
    if (btnOverlay != null)
      return;
    
    // mgmaps lib hack
    final APPPlace place = findCenteredPlace();
    if (place == null)
      return;
    
    selectedPlace = place;
    placeClickedAction();
  }
  
  public void mapMoved() {
    mapMoved(false);
  }

  public void mapMoved(boolean force) {
    if (!initialized || !mapInitialized)
      return;
    
    if (btnOverlay != null)
      return;
    
    // dynamic places
    if (APPSettings.featureDynamicPlaces)
      downloadPlaces(force);
    
    invalidate(INV_MAP);
  }
  
  public void showAlert(String string) {
    showAlert(string, string);
  }
  
  public void showAlert(String t, String string) {
    showAlert(t, string, APPSettings.alertTimeout);
  }

  /**
   * Show an alert form for a few seconds.
   * @param string alert text
   */
  public void showAlert(String t, String string, int timeout) {
    final Alert alert = new Alert(t, string, null, AlertType.INFO);
    alert.setTimeout(timeout);
    Display.getDisplay(APPMidlet.instance).setCurrent(alert);
  }
  
  protected void setMapType() {
    setMapType(APPSettings.savePointT);
  }
  
  public void setMapType(int mt) {
    mapType = mt;
    map.setMap(getGeoMap(mt));
    invalidate(INV_MAP);
  }

  public GPSPlace getGPSPlace() {
    if (gps == null)
      return null;
    
    return gps.getPlace();
  }
  
  public GPSReceiver getGPSReceiver() {
    if (gps == null)
      return null;
    
    return gps.getReceiver();
  }
  
  public void setGPSReceiver(boolean force) {
    gps.setReceiver(force);
  }
}
